import React, { useLayoutEffect } from 'react';
import { Router, useRoutes } from 'react-router-dom';
import ReactDOM from 'react-dom/client';
import { createClientRoutes } from './routes';
import { AppContext, useAppData } from './appContext';
import { dataflowProvider } from '@supcon/supcon-microapp';
import type { History } from 'history';
import type { IRoutesById, IRouteComponents } from './types';

let root: ReactDOM.Root | null = null;
let rootElement: HTMLElement

export function __getRoot() {
    return root;
}

export function __getRootElement() {
    return rootElement;
}

export function Routes() {
    const { clientRoutes } = useAppData();
    return useRoutes(clientRoutes as any);
}

function BrowserRoutes(props: {
    routes: any;
    clientRoutes: any;
    pluginManager: any;
    history: History;
    basename: string;
    children: any;
}) {
    const { history } = props;
    const [state, setState] = React.useState({
        action: history.action,
        location: history.location,
    });
    useLayoutEffect(() => history.listen(setState), [history]);
    useLayoutEffect(() => {
        function onRouteChange(opts: any) {
            props.pluginManager.applyPlugins({
                key: 'onRouteChange',
                type: 'event',
                args: {
                    routes: props.routes,
                    clientRoutes: props.clientRoutes,
                    location: opts.location,
                    action: opts.action,
                    basename: props.basename,
                    isFirst: Boolean(opts.isFirst),
                },
            });
        }
        onRouteChange({
            location: state.location,
            action: state.action,
            isFirst: true,
        });
        return history.listen(onRouteChange);
    }, [history, props.routes, props.clientRoutes]);
    return (
        <Router
            navigator={history}
            location={state.location}
            basename={props.basename}
        >
            {props.children}
        </Router>
    );
}

const getBrowser = (
    opts: RenderClientOpts,
    routesElement: React.ReactElement,
) => {
    const basename = opts.basename || '/';
    const clientRoutes = createClientRoutes({
        routesById: opts.routes,
        routeComponents: opts.routeComponents,
        loadingComponent: opts.loadingComponent,
        reactRouter5Compat: opts.reactRouter5Compat === undefined ? true : opts.reactRouter5Compat,
    });
    opts.pluginManager.applyPlugins({
        key: 'patchClientRoutes',
        type: 'event',
        args: {
            routes: clientRoutes,
        },
    });
    let rootContainer = (
        <BrowserRoutes
            basename={basename}
            routes={opts.routes}
            pluginManager={opts.pluginManager}
            clientRoutes={clientRoutes}
            history={opts.history}
        >
            {routesElement}
        </BrowserRoutes>
    );
    const Browser = () => {
        useLayoutEffect(() => {
            if (typeof opts.callback === 'function') opts.callback();
        }, []);
        return (
            <AppContext.Provider
                value={{
                    routes: opts.routes,
                    routeComponents: opts.routeComponents,
                    clientRoutes,
                    basename,
                    pluginManager: opts.pluginManager,
                    history: opts.history,
                    rootElement: opts.rootElement
                }}
            >
                {dataflowProvider(rootContainer, opts)}
            </AppContext.Provider>
        );
    }
    return Browser
};

export type RenderClientOpts = {
    basename?: string;
    loadingComponent?: React.ReactNode;
    reactRouter5Compat?: boolean;
    rootElement?: HTMLElement;
    hydrate?: boolean;
    components?: boolean;
    pluginManager: any;
    history: History;
    routes: IRoutesById;
    routeComponents: IRouteComponents;
    callback?: () => void;
};

export function renderClient(opts: RenderClientOpts) {
    rootElement = opts.rootElement || document.getElementById('root')!;
    const Browser = getBrowser(opts, <Routes />);
    if (opts.components) return Browser;
    if (opts.hydrate) {
        ReactDOM.hydrateRoot(rootElement, <Browser />);
        return;
    }
    root = ReactDOM.createRoot(rootElement);
    root.render(<Browser />);
    return;
}